package pub.gll.onepeas.libbase.track.manager

import android.graphics.Rect
import android.view.View
import androidx.collection.ArrayMap
import pub.gll.onepeas.libbase.track.util.KKExposureThreadUtil
import pub.gll.onepeas.libbase.track.api.IViewExposure
import pub.gll.onepeas.libbase.track.constants.ViewExposureConstants
import pub.gll.onepeas.libbase.track.holder.ExposureHolder
import pub.gll.onepeas.libbase.track.holder.ExposureStrategyFactory
import pub.gll.onepeas.libbase.track.model.ExposureInner
import pub.gll.onepeas.libbase.track.model.ExposureModel
import pub.gll.onepeas.libbase.track.ui.KKTrackerExposureLayout
import java.util.*
import kotlin.collections.HashSet

/**
 * <pre>
 *     @author : whg
 *     @e-mail : guanhuawei@kkworld.com
 *     @time   : 2021/6/17 16:39
 *     @desc   : 曝光主要逻辑实现类
 *     @version: 1.0
 * </pre>
 */
internal class KKExposureManager {

    private var mExposureHolder: ExposureHolder? = null

    private val DEFAULT_DIM_THRESHOLD: Float = 0.7f

    val ITEM_ID = "ItemId"

    /**
     * 自增id
     */
    @Volatile
    var KKSessionCommonItemImpId = System.currentTimeMillis()

    /**
     * 遍历间隔时间
     */
    private var traverseTime: Long = 0

    /**
     * KKTrackerExposureLayout为key，value是绑定过tag的view
     */
    private val bindViewByKKLayout: MutableMap<View, WeakHashMap<View, Any>?> = WeakHashMap()

    /**
     * 根据view状态的时机变化，开始遍历viewtree,并上报上报数据
     * @param triggerType Int 时机类型
     * @param view View? 父view
     * @param lastVisibleViewMap MutableMap<String, ExposureModel> 上一次可见的view和对应view绑定的数据
     */
    fun triggerViewCalculate(
        triggerType: Int,
        view: View?,
        isVisible: Boolean,
        clearAll: Boolean,
        lastVisibleViewMap: MutableMap<Any, ExposureModel>
    ) {
        //两次遍历时间不能小于100ms
        val triggerTime = System.currentTimeMillis()
        if (triggerTime - traverseTime < 200) {
            return
        }

        traverseTime = triggerTime

        //view 为空返回
        if (view == null) {
            return
        }

        //当前可见的view
        val currentVisibleViewMap: MutableMap<Any, ExposureModel> = ArrayMap()

        //遍历view tree
        traverseViewTree(view, lastVisibleViewMap, currentVisibleViewMap)

        //处理上次可见的view和当前可见的view
        commitExposure(triggerType, isVisible, clearAll, lastVisibleViewMap, currentVisibleViewMap)
    }

    /**
     * 遍历屏幕中所有可见的view
     * @param view View
     * @param lastVisibleViewMap Map<String, ExposureModel>
     * @param currentVisibleViewMap MutableMap<String, ExposureModel>
     */
    private fun traverseViewTree(
        view: View?,
        lastVisibleViewMap: Map<Any, ExposureModel>,
        currentVisibleViewMap: MutableMap<Any, ExposureModel>
    ) {
        if (view == null) return

        val viewMap = bindViewByKKLayout[view]
        viewMap?.forEach {
            //记录有tag的view
            if (isViewHasTag(it.key)) {
                wrapExposureCurrentView(it.key, lastVisibleViewMap, currentVisibleViewMap)
            }
        }
    }

    /**
     * 判断当前view是否有绑定id
     * @param view View
     * @return Boolean
     */
    private fun isViewHasTag(view: View): Boolean {
        return view.getTag(ViewExposureConstants.VIEW_TAG_UNIQUE_NAME) != null
    }

    /**
     * 记录当前曝光的view
     * @param view View
     * @param lastVisibleViewMap Map<String, ExposureModel>
     * @param currentVisibleViewMap MutableMap<String, ExposureModel>
     */
    private fun wrapExposureCurrentView(
        view: View?,
        lastVisibleViewMap: Map<Any, ExposureModel>,
        currentVisibleViewMap: MutableMap<Any, ExposureModel>
    ) {
        if (view == null) return
        //没有绑定id的直接return
        val viewTag = view.getTag(ViewExposureConstants.VIEW_TAG_UNIQUE_NAME) ?: return
        //没有绑定数据的直接return
        val params = view.getTag(ViewExposureConstants.VIEW_TAG_PARAM) as? HashMap<String, Any>

        var dimThreshold = view.getTag(ViewExposureConstants.VIEW_TAG_DIM_THRESHOLD) as? Float
        if (dimThreshold == null) {
            dimThreshold = DEFAULT_DIM_THRESHOLD
        }

        //是否可见
        val isWindowChange = view.hasWindowFocus()
        //可见区域
        val exposureValid = isVisibleByDimension(view, dimThreshold)

        if (!(isWindowChange && exposureValid)) {
            return
        }
        //获取view设置的listener
        val listener = view.getTag(ViewExposureConstants.VIEW_TAG_LISTENER) as? IViewExposure

        //只添加屏幕可见的view
        if (lastVisibleViewMap.containsKey(viewTag)) {
            lastVisibleViewMap[viewTag]?.let {
                currentVisibleViewMap[viewTag] = it
            }
        } else if (!currentVisibleViewMap.containsKey(viewTag)) {
            ExposureModel.obtain(ExposureModel().also {
                it.beginTime = System.currentTimeMillis()
                it.tag = viewTag
                it.view = view
                it.params = params
                it.listener = listener
                it.dimThreshold = dimThreshold
            }).let {
                itemIdPlus(it)
                currentVisibleViewMap[viewTag] = it
            }
        }
    }

    private fun itemIdPlus(exposureModel: ExposureModel?) {
        exposureModel?.let {
            var params = it.params
            if (params == null) {
                //每个view的曝光开始自带自增id
                params = HashMap<String, Any>()
            }
            params[ITEM_ID] = ++KKSessionCommonItemImpId
        }
    }

    /**
     * 获取曝光的view后，处理曝光的view，对外暴露时机或者做上报
     * @param triggerType Int
     * @param lastVisibleViewMap MutableMap<String, ExposureModel>
     * @param currentVisibleViewMap Map<String, ExposureModel>
     */
    private fun commitExposure(
        triggerType: Int,
        isVisible: Boolean,
        clearAll: Boolean,
        lastVisibleViewMap: MutableMap<Any, ExposureModel>,
        currentVisibleViewMap: Map<Any, ExposureModel>
    ) {
        val exposureInner = ExposureInner()
        exposureInner.triggerType = triggerType
        mExposureHolder = ExposureStrategyFactory.create(isVisible)

        //上一次的可见ViewMap
        exposureInner.lastVisibleViewMap = mutableMapOf()
        for ((key, value) in lastVisibleViewMap) {
            (value.clone() as? ExposureModel)?.also { exposureInner.lastVisibleViewMap!![key] = it }
        }

        //当前可见的ViewMap
        exposureInner.currentVisibleViewMap = mutableMapOf()
        for ((key, value) in currentVisibleViewMap) {
            (value.clone() as? ExposureModel)?.also {
                exposureInner.currentVisibleViewMap!![key] = it
            }
        }

        //根据时机判断是否清除所有
        clearLastVisibleView(clearAll, lastVisibleViewMap)

        lastVisibleViewMap.putAll(currentVisibleViewMap)

        KKExposureThreadUtil.handlerExposureMessage(
            ExposureHandlerRunnable(
                mExposureHolder,
                SINGLE_COMMIT_EXPOSURE,
                exposureInner
            )
        )
    }

    /**
     * 单独处理一些时机时不可见的view
     * @param clearAll Boolean
     * @param lastVisibleViewMap MutableMap<Any, ExposureModel>
     */
    private fun clearLastVisibleView(
        clearAll: Boolean,
        lastVisibleViewMap: MutableMap<Any, ExposureModel>
    ) {
        if (lastVisibleViewMap.isEmpty()) {
            return
        }
        //记录需要删除的key
        //清除所有不可见的view
        val keySet = gatherViewToSet(clearAll, lastVisibleViewMap)
        if (clearAll) {
            lastVisibleViewMap.clear()
        } else {
            keySet.forEach {
                lastVisibleViewMap.remove(it)
            }
        }

    }

    private fun gatherViewToSet(
        clear: Boolean,
        lastVisibleViewMap: MutableMap<Any, ExposureModel>
    ): HashSet<Any> {
        val keySet = HashSet<Any>()
        for (mutableEntry in lastVisibleViewMap) {
            val key = mutableEntry.key
            val value = mutableEntry.value
            if (isInVisible(value.view)) {
                notifyViewInVisible(value)
                if (!clear) {
                    keySet.add(key)
                }
            }

        }
        return keySet
    }

    /**
     * 在滑动过程中需要单独处理
     * @param exposureModel ExposureModel?
     */
    private fun notifyViewInVisible(exposureModel: ExposureModel?) {

        KKExposureThreadUtil.handlerExposureMessage(
            ExposureHandlerRunnable(
                mExposureHolder,
                HANDLE_VIEW_DISEXPOSURE,
                exposureModel
            )
        )
    }

    /**
     * 一定时机进行删除所有上次可见的view
     */
    fun clearLastVisibleViewMap(lastVisibleViewMap: MutableMap<Any, ExposureModel>?) {
        lastVisibleViewMap?.let { clearLastVisibleView(true, it) }
    }

    /**
     * 判断view是否不可见
     */
    private fun isInVisible(view: View?): Boolean {
        if (view == null) return true
        val globalVisibleRect = Rect()
        val isVisibleRect = view.getLocalVisibleRect(globalVisibleRect)
        val hasShown = view.isShown
        val hasWindowFocus = view.hasWindowFocus()
        return !isVisibleRect || !hasShown || !hasWindowFocus
    }


    /**
     * view的可见区域判断，目前只能统一设置，默认（70%）
     * @param view View
     * @return Boolean
     */
    private fun isVisibleByDimension(view: View, dimThreshold: Float): Boolean {
        val width = view.width
        val height = view.height
        val globalVisibleRect = Rect()
        val isVisibleRect = view.getGlobalVisibleRect(globalVisibleRect)
        val isShow = view.visibility == View.VISIBLE
        val hasShown = view.isShown
        return if (isVisibleRect && isShow && hasShown) {
            val visibleWidth = globalVisibleRect.width()
            val visibleHeight = globalVisibleRect.height()
            visibleWidth * 1.00 / width > dimThreshold && visibleHeight * 1.00 / height >= dimThreshold
        } else {
            false
        }
    }

    /**
     * 记录绑定的view
     * add时机做优化
     */
    fun addBindView(layout: View?, view: View) {
        layout?.let {
            var viewMap = bindViewByKKLayout[layout]
            if (viewMap.isNullOrEmpty()) {
                viewMap = WeakHashMap()
            }
            if (!viewMap.contains(view)) {
                viewMap[view] = Any()
            }
            bindViewByKKLayout[layout] = viewMap
        }
    }

    /**
     * 删除绑定的view
     */
    fun removeBindView(layout: View?, view: View) {
        layout?.let {
            val viewMap = bindViewByKKLayout[layout]
            viewMap?.let {
                if (viewMap.contains(view)) {
                    viewMap.remove(view)
                }
            }
        }
    }

    /**
     * 删除数据所有绑定的数据
     */
    fun clearBindViews(layout: View?) {
        layout?.let {
            val viewMap = bindViewByKKLayout[layout]
            viewMap?.let {
                viewMap.clear()
            }
        }
        KKExposurePageManager.removeExposureManager(layout)
    }

    /**
     * 绑定数据前进行时机问题的修复
     * @param layout View? 当前页面
     * @param view View  绑定的view
     * @param newTag Any  view绑定的tag
     */
    fun checkBindStatus(layout: View?, view: View, newTag: Any) {
        layout?.let {
            val viewMap = bindViewByKKLayout[layout]
            if (viewMap.isNullOrEmpty() || !viewMap.containsKey(view)) {
                return
            }
            val oldTag = view.getTag(ViewExposureConstants.VIEW_TAG_UNIQUE_NAME) ?: return
            val params =
                view.getTag(ViewExposureConstants.VIEW_TAG_PARAM) as? HashMap<String, Any>
            val listener =
                view.getTag(ViewExposureConstants.VIEW_TAG_LISTENER) as? IViewExposure
            if (oldTag == newTag || layout !is KKTrackerExposureLayout) {
                return
            }
            val lastVisibleViewMap = layout.lastVisibleViewMap
            if (lastVisibleViewMap.containsKey(oldTag)) {
                lastVisibleViewMap[oldTag]?.also {
                    it.params = params
                    it.listener = listener
                }?.let {
                    notifyViewInVisible(it)
                }
            }
        }
    }


    companion object {
        /**
         * 单个上传
         */
        const val SINGLE_COMMIT_EXPOSURE = 0

        const val HANDLE_VIEW_DISEXPOSURE = 1

    }
}